/* Copyright (c) 2020 Janux Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Based on an original project for the MAX72xx LED matrix and FFT lib made from Shajeeb. Configuration settings section based on work of Ragnar Ranøyen Homb from Norvegian Creation. */ #define LIN_OUT 1 //FHT linear output magnitude #define FHT_N 128 //set SAMPLES for FHT, Must be a power of 2 #include #define xres 32 //Total number of columns in the display, must be <= SAMPLES/2 #define yres 8 //Total number of rows in the display #define ledPIN 6 //out pint to control Leds #define NUM_LEDS (xres * yres) //total leds in Matrix #include #include #include #define colorPIN 5 //pin to change ledcolor #define brightnessPIN 10 //pin to change brightness byte displaycolor = 0; //default color value byte brightness = 1; //default brightness level #include #define CONFIG_START 32 //Memory start location #define CONFIG_VERSION "VER01" //Config version configuration typedef struct { char version[6]; byte displaycolor; byte brightness; } configuration_type; configuration_type CONFIGURATION = { CONFIG_VERSION, displaycolor, brightness }; byte yvalue; int peaks[xres]; byte state = HIGH; // the current reading from the input pin byte previousState = LOW; // the previous reading from the input pin unsigned long lastDebounceTime = 0; // the last time the output pin was toggled unsigned long debounceDelay = 100; // the debounce time; increase if the output flickers byte data_avgs[xres]; //Array for samplig // Parameter 1 = number of leds in matrix // Parameter 2 = pin number // Parameter 3 = pixel type flags, add together as needed: // NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) // NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) // NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) // NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) Adafruit_NeoPixel pixel = Adafruit_NeoPixel(NUM_LEDS, ledPIN, NEO_GRB + NEO_KHZ800); // EQ filter byte eq[32] = { 60, 65, 70, 75, 80, 85, 90, 95, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 115, 125, 140, 160, 185, 200, 200, 200 }; bool EQ_ON = true; // set to false to disable eq //Define 5 set of colors for leds, 0 for single custom color byte colors[][8] = { {170, 160, 150, 140, 130, 120, 1, 1}, {1, 5, 10, 15, 20, 25, 90, 90}, {90, 85, 80, 75, 70, 65, 1, 1}, {90, 90, 90, 30, 30, 30, 1, 1}, {170, 160, 150, 140, 130, 120, 110, 0} }; //Define chars for display settings byte charBitmap[] = { 0x1C, 0x10, 0x10, 0x10, 0x10, 0x1C, 0x08, 0x18, 0x08, 0x08, 0x08, 0x1C, 0x0C, 0x12, 0x04, 0x08, 0x10, 0x1E, 0x0C, 0x12, 0x02, 0x06, 0x12, 0x0C, 0x10, 0x10, 0x10, 0x14, 0x1E, 0x04, 0x1E, 0x10, 0x1E, 0x02, 0x12, 0x0C, 0x1E, 0x10, 0x10, 0x1E, 0x12, 0x1E, 0x1E, 0x02, 0x04, 0x08, 0x08, 0x08, 0x0C, 0x12, 0x0C, 0x12, 0x12, 0x0C, 0x1C, 0x12, 0x1C, 0x12, 0x12, 0x1C }; void setup() { pixel.begin(); //initialize Led Matrix //Begin FFT operations ADCSRA = 0b11100101; // set ADC to free running mode and set pre-scaler to 32 (0xe5) ADMUX = 0b00000000; // use pin A0 and external voltage reference // Read config data from EEPROM if (loadConfig()) { displaycolor = CONFIGURATION.displaycolor; brightness = CONFIGURATION.brightness; } //Set brightness loaded from EEPROM pixel.setBrightness(brightness * 24 + 8); //Show current config on start //change true to false if you don't want this showSettings(3, true); } void loop() { while (1) { // reduces jitter Sampling(); // FHT Library use only one data array RearrangeFHT(); // re-arrange FHT result to match with no. of display columns SendToDisplay(); // send to display according measured value colorChange(); // check if button pressed to change color brightnessChange(); // check if button pressed to change brightness delay(10); // delay to reduce flickering (FHT is too fast :D) } } void Sampling() { for (int i = 0; i < FHT_N; i++) { while (!(ADCSRA & 0x10)); // wait for ADC to complete current conversion ie ADIF bit set ADCSRA = 0xf6 ; // clear ADIF bit so that ADC can do next operation (0xf5) //ADLAR bit is 0, so the 10 bits of ADC Data registers are right aligned byte m = ADCL; // fetch adc data byte j = ADCH; int value = (j << 8) | m; // form into an int value -= 0x0200; // form into a signed int value <<= 6; // form into a 16b signed int fht_input[i] = value / 8; // copy to fht input array after compressing } // ++ begin FHT data process -+-+--+-+--+-+--+-+--+-+--+-+--+-+- fht_window(); // window the data for better frequency response fht_reorder(); // reorder the data before doing the fht fht_run(); // process the data in the fht fht_mag_lin(); // take the output of the fht } void RearrangeFHT() { // FHT return real value unsing only one array // after fht_mag_lin() calling the samples value are in // the first FHT_N/2 position of the array fht_lin_out[] int step = (FHT_N / 2) / xres; int c = 0; for (int i = 0; i < (FHT_N / 2); i += step) { data_avgs[c] = 0; for (int k = 0 ; k < step ; k++) { data_avgs[c] = data_avgs[c] + fht_lin_out[i + k]; // linear output magnitude } data_avgs[c] = data_avgs[c] / step ; // save avgs value c++; } } void SendToDisplay() { for (int i = 0; i < xres; i++) { if (EQ_ON) data_avgs[i] = data_avgs[i] * (float)(eq[i]) / 100; // apply eq filter data_avgs[i] = constrain(data_avgs[i], 0, 80); // set max & min values for buckets to 0-80 data_avgs[i] = map(data_avgs[i], 0, 80, 0, yres); // remap averaged values to yres 0-8 yvalue = data_avgs[i]; peaks[i] = peaks[i] - 1; // decay by one light if (yvalue > peaks[i]) peaks[i] = yvalue; // save peak if > previuos peak yvalue = peaks[i]; // pick peak to display setColumn(i, yvalue); // draw columns } pixel.show(); // show column } // Light up leds of x column according to y value void setColumn(byte x, byte y) { int led, i; for (i = 0; i < yres; i++) { led = GetLedFromMatrix(x, i); //retrieve current led by x,y coordinates if (peaks[x] > i) { switch (displaycolor) { case 4: if (colors[displaycolor][i] == 0) { // show custom color with zero value in array pixel.setPixelColor(led, 255, 255, 255); //withe } else { // standard color defined in colors array pixel.setPixelColor(led, Wheel(colors[displaycolor][i])); } break; case 5: //change color by column pixel.setPixelColor(led, Wheel(x * 16)); break; case 6: //change color by row pixel.setPixelColor(led, Wheel(i * y * 3)); break; case 7: //change color by... country :D //Italy flagh //if (x < 11) pixel.setPixelColor(led, 0, 255, 0); //if (x > 10 && x < 21) pixel.setPixelColor(led, 255, 255, 255); //if (x > 20) pixel.setPixelColor(led, 255, 0, 0); //stars and stripes if (i < yres - 2) { if (x & 0x01) { pixel.setPixelColor(led, 0, 0, 255); } else { pixel.setPixelColor(led, 255, 0, 0); } } else { pixel.setPixelColor(led, 255, 255, 255); } break; default: //display colors defined in color array pixel.setPixelColor(led, Wheel(colors[displaycolor][i])); } //END SWITCH } else { //Light off leds pixel.setPixelColor(led, pixel.Color(0, 0, 0)); } } } //================================================================ // Calculate a led number by x,y coordinates // valid for WS2812B with serpentine layout placed in horizzontal // and zero led at bottom right (DIN connector on the right side) // input value: x= 0 to xres-1 , y= 0 to yres-1 // return a led number from 0 to NUM_LED //================================================================ int GetLedFromMatrix(byte x, byte y) { int led; x = xres - x - 1; if (x & 0x01) { //Odd columns increase backwards led = ((x + 1) * yres - y - 1 ); } else { //Even columns increase normally led = ((x + 1) * yres - yres + y); } return constrain(led, 0, NUM_LEDS); } //================================================================ void colorChange() { int reading = digitalRead(colorPIN); if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay) { displaycolor++; if (displaycolor > 7) displaycolor = 0; showSettings(1, true); //set to false if you don't want this saveConfig(); lastDebounceTime = millis(); } previousState = reading; } void brightnessChange() { int reading = digitalRead(brightnessPIN); if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay) { brightness++; if (brightness > 7) brightness = 0; pixel.setBrightness(brightness * 24 + 8); showSettings(2, true); //set to false if you don't want this saveConfig(); lastDebounceTime = millis(); } previousState = reading; } // Utility from Adafruit Neopixel demo sketch // Input a value 0 to 255 to get a color value. // The colours are a transition R - G - B - back to R. unsigned long Wheel(byte WheelPos) { WheelPos = 255 - WheelPos; if (WheelPos < 85) { return pixel.Color(255 - WheelPos * 3, 0, WheelPos * 3); } if (WheelPos < 170) { WheelPos -= 85; return pixel.Color(0, WheelPos * 3, 255 - WheelPos * 3); } WheelPos -= 170; return pixel.Color(WheelPos * 3, 255 - WheelPos * 3, 0); } // load whats in EEPROM in to the local CONFIGURATION if it is a valid setting int loadConfig() { if (EEPROM.read(CONFIG_START + 0) == CONFIG_VERSION[0] && EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1] && EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2] && EEPROM.read(CONFIG_START + 3) == CONFIG_VERSION[3] && EEPROM.read(CONFIG_START + 4) == CONFIG_VERSION[4]) { // load (overwrite) the local configuration struct for (unsigned int i = 0; i < sizeof(CONFIGURATION); i++) { *((char*)&CONFIGURATION + i) = EEPROM.read(CONFIG_START + i); } return 1; // return 1 if config loaded } return 0; // return 0 if config NOT loaded } // save the CONFIGURATION in to EEPROM void saveConfig() { CONFIGURATION.displaycolor = displaycolor; CONFIGURATION.brightness = brightness; for (unsigned int i = 0; i < sizeof(CONFIGURATION); i++) EEPROM.write(CONFIG_START + i, *((char*)&CONFIGURATION + i)); } // 1 display color level, 2 display brightness level, 3 both void showSettings(byte num, bool show) { if (show) { pixel.clear(); if (num == 1 || num == 3) { drawChar(0, 0); drawChar(displaycolor + 1, 5); } if (num == 2 || num == 3) { drawChar(9, xres - 9); drawChar(brightness + 1, xres - 4); } delay(1000); pixel.clear(); } } // Draw custom chars void drawChar(byte val, byte pos) { for (int x = 4; x >= 0; x--) { for (int y = 5; y >= 0; y--) { if ((charBitmap[val * 6 + 5 - y] >> x) & 0x01) { pixel.setPixelColor(GetLedFromMatrix(4 - x + pos, y + 1), Wheel((pos > 10) * 170)); pixel.show(); } } } } //by Janux®, Last version on 28/06/2020.